﻿using System;
using System.Collections.Generic;

using FireBus.Contracts;
using FireBus.Core;
using FireBus.Shell.Contracts;
using FireBus.Core.Configuration;
using FireBus.Shell.Properties;


namespace FireBus.Shell
{
	class Program
	{
		private static Host _Host;

		private static IDictionary<String, ShellCommandInfo> _Commands
			= new SortedDictionary<String, ShellCommandInfo>();

		private static readonly Object _Sync = new Object();

		[STAThread]
		[LoaderOptimization(LoaderOptimization.MultiDomainHost)]
		static void Main()
		{
			var config = new XmlConfiguration();
			using (_Host = new Host(config))
			{
				SubscribeToEvents();
				AddExtensions();
				PrintInfo();
				ReadAndHandle();
				UnsubscribeFromEvents();
			}
		}

		private static void SubscribeToEvents()
		{
			_Host.ExtensionLoaded += OnHostExtensionLoaded;
			_Host.ExtensionUnloaded += OnHostExtensionUnloaded;
		}

		private static void UnsubscribeFromEvents()
		{
			_Host.ExtensionLoaded -= OnHostExtensionLoaded;
			_Host.ExtensionUnloaded -= OnHostExtensionUnloaded;
		}

		private static void OnHostExtensionLoaded(Object sender, ExtensionEventArgs e)
		{
			if (!e.Metadata.CanListenToRequest(
				typeof(CommandListRequest).AssemblyQualifiedName,
				typeof(CommandListResponse).AssemblyQualifiedName))
			{
				return;
			}
			var request = new CommandListRequest();
			_Host.BeginRequest<CommandListRequest, CommandListResponse>(
				e.Extension, request, OnCommandListRequested, e.Extension);
		}

		private static void OnCommandListRequested(IAsyncResult asyncResult)
		{
			var response = _Host.EndRequest<CommandListResponse>(asyncResult);
			var extensionName = (ExtensionName) asyncResult.AsyncState;
			lock (_Sync)
			{
				foreach (var commandToken in response.Commands)
				{
					_Commands.Add(
						commandToken.Name,
						new ShellCommandInfo
						{
							Token = commandToken,
							Extension = extensionName
						});
				}
			}
		}

		private static void OnHostExtensionUnloaded(Object sender, ExtensionEventArgs e)
		{
			RemoveExtensionCommands(e.Extension);
		}

		private static void RemoveExtensionCommands(ExtensionName extensionName)
		{
			lock (_Sync)
			{
				var commands = new List<ShellCommandInfo>(_Commands.Values);
				foreach (var commandInfo in commands)
				{
					if (commandInfo.Extension.Equals(extensionName))
					{
						_Commands.Remove(commandInfo.Token.Name);
					}
				}
			}
		}

		private static String Read()
		{
			var text = Console.ReadLine();
			return text;
		}

		private static void AddExtensions()
		{
			_Host.BeginLoadAllExtensions(null, null);
		}

		private static void PrintInfo()
		{
			Console.WriteLine(Resources.FIREBUS);
			Console.WriteLine();
			Console.WriteLine(Resources.TYPE_HELP_OR_EXIT);
		}

		private static void ReadAndHandle()
		{
			var text = Read();
			while (text != "exit")
			{
				if (text == "help")
				{
					PrintAvailableCommands();
				}
				else
				{
					var args = text.Split(' ');
					if (args.Length >= 1)
					{
						var commandName = args[0];
						ShellCommandInfo command;
						lock (_Sync)
						{
							_Commands.TryGetValue(commandName, out command);
						}
						if (command == null)
						{
							Console.WriteLine(Resources.COMMAND_NOT_FOUND);
						}
						else
						{
							if (args.Length == 2 &&
								args[1] == "?")
							{
								PrintCommandHelp(command);
							}
							else
							{
								var arguments = ParseArgs(text);
								ExecuteCommand(
									command.Extension, command.Token.Name, arguments);
							}
						}
					}
				}

				text = Read();
			}
		}

		private static void PrintAvailableCommands()
		{
			lock (_Sync)
			{
				if (_Commands.Count == 0)
				{
					Console.WriteLine(
						Resources.THERE_ARE_NO_AVAILABLE_COMMANDS);
				}
				else
				{
					Console.WriteLine(
						Resources.LIST_OF_AVAILABLE_COMMANDS);
					foreach (var command in _Commands)
					{
						Console.WriteLine(command.Value.Token.Name);
					}
					Console.WriteLine(
						Resources.TYPE_COMMAND_NAME_TO_VIEW_COMMAND_DESCRIPTION);
				}
			}
		}

		private static void PrintCommandHelp(ShellCommandInfo command)
		{
			Console.WriteLine(command.Token.Name);
			Console.WriteLine(command.Token.Description);
		}

		private static IEnumerable<String> ParseArgs(String text)
		{
			var args = new List<String>(
				text.SplitCommandLine());
			if (args.Count > 0)
			{
				args.RemoveAt(0);
			}
			return args;
		}

		private static void ExecuteCommand(
			ExtensionName extensionName, ShellCommandName commandName, IEnumerable<String> args)
		{
			var eventArgs = new CommandRequest(
				commandName,
				args);
			_Host.BeginRequest<CommandRequest, CommandResponse>(extensionName, eventArgs, OnCommandExecuted, null);
		}

		private static void OnCommandExecuted(IAsyncResult asyncResult)
		{
			var response = _Host.EndRequest<CommandResponse>(asyncResult);
			Console.WriteLine(response.Output);
		}
	}
}
